package main;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 
 * Represents the partition of the graph. Provides methods to manipulate the
 * partition.
 * 
 */
public class Partition {

	/**
	 * Creates a new partition.
	 * 
	 * @param listBlock
	 *            list of blocks the build the partition.
	 */
	public Partition(List<Block> listBlock) {
		this.listBlock = listBlock;
	}

	private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
	private final Lock read = rwLock.readLock();
	private final Lock write = rwLock.writeLock();

	/**
	 * List of blocks in a partition. Guarded by rwLock.
	 */
	private List<Block> listBlock;
	/**
	 * THe last unstable block or null. ThreadUnsafe. Only for sequential use.
	 */
	private Block lastUnstableBlock = null;

	/**
	 * Returns the number of blocks in this partition.
	 * 
	 * @return size
	 */
	public int size() {
		read.lock();
		try {
			return this.listBlock.size();
		} finally {
			read.unlock();
		}
	}

	/**
	 * Gives true, if some unstable or taken block exists.
	 * 
	 * @return
	 */
	public boolean hasUnstableOrTakenBlock() {
		read.lock();
		try {
			boolean isSomeTaken = false;

			for (Block block : this.listBlock) {
				if (!block.isStable()) {
					lastUnstableBlock = block;
					return true;
				}
				if (block.isTaken())
					isSomeTaken = true;
			}

			if (isSomeTaken)
				return true;

			lastUnstableBlock = null;
			return false;
		} finally {

			read.unlock();
		}
	}

	/**
	 * Gives an unstable block. ThreadUnsafe. Only for sequential use. Call
	 * hasUnfixedOrTakenBlock before.
	 * 
	 * @return
	 */
	public Block getUnstableBlockSeq() {

		return lastUnstableBlock;
	}

	/**
	 * Takes an unstable block if any exists.
	 * 
	 * @return unstable block or null
	 */
	public Block getUnstableBlock() {
		read.lock();
		try {
			for (Block candidate : this.listBlock) {
				if (candidate.tryTake()) {
					return candidate;
				}
			}
			return null;
		} finally {
			read.unlock();
		}
	}

	/**
	 * Split the removeBlock into two new blocks: addBlock1 and addBlock2.
	 * 
	 * @param removeBlock
	 *            the block that should be removed from the partition.
	 * @param addBlock1
	 *            the block that is added to the partition.
	 * @param addBlock2
	 *            the block that is added to the partition.
	 * @return true
	 */
	public boolean split(Block removeBlock, Block addBlock1, Block addBlock2) {
		write.lock();
		try {
			this.destablePreBlocks(removeBlock);

			this.listBlock.remove(removeBlock);

			addBlock1.commit();
			addBlock2.commit();
			this.listBlock.add(addBlock1);
			this.listBlock.add(addBlock2);

			return true;
		} finally {
			write.unlock();
		}
	}

	/**
	 * All pre-blocks should be set to unstable.
	 * 
	 * @param block
	 *            block of which to take the pre-block.
	 */
	private void destablePreBlocks(Block block) {
		for (Node node : block.getNodeSet()) {
			for (Node preNode : node.getPredNodeSet()) {
				preNode.getBlock().destable();
			}
		}
	}

	/**
	 * Gets the post-blocks for a block.
	 * 
	 * @param block
	 *            block for which to get the post-blocks.
	 * @return list of post-blocks.
	 */
	public List<Block> getPostBlocks(Block block) {
		read.lock();
		try {
			List<Block> postBlocks = new LinkedList<Block>();

			for (Node node : block.getNodeSet()) {
				for (Node postNode : node.getPostNodeSet())
					if (!postBlocks.contains(postNode.getBlock())
							&& !block.getIrrelevantPostBlocks().contains(
									postNode.getBlock()))

						postBlocks.add(postNode.getBlock());
			}

			return postBlocks;
		} finally {
			read.unlock();
		}
	}

	/**
	 * String representation of the partition.
	 */
	public String toString() {
		read.lock();
		try {
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.append("{");
			if (listBlock.size() > 0)
				stringBuilder.append(this.listBlock.get(0).toString());
			for (int i = 1; i < listBlock.size(); i++)
				stringBuilder.append(", " + listBlock.get(i).toString());
			stringBuilder.append("}");
			return stringBuilder.toString();
		} finally {
			read.unlock();
		}
	}
}
